package assignment4.part2;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

import assignment4.part1.Decrypt;
import assignment4.part1.Encrypt;

public class B {
	public static String key = "__md82([9222\"0";
	
	private final int port;
	private ServerSocket socket;
	private boolean isClosed = false;
	
	public B(int port){
		this.port = port;
		try{
			this.socket = new ServerSocket(this.port);
			System.out.println("Server bound to port: " + this.port);
		}catch(IOException iex){
			System.out.println("Failed to initialize server socket on port: " + this.port);
			System.exit(-1);
		}
	}
	
	/**
	 * Start the server and wait for clients to connect.
	 * When connected start a thread handling the client interaction.
	 * @return True if the server started successfully, false if not.
	 */
	public boolean start(){
		new Thread(new Runnable(){
			@Override
			public void run() {
				while(!isClosed){
					try{
						System.out.println("Waiting for client to connect...");
						Socket client = socket.accept();
						System.out.println("Client connected!");
						new Thread(new ClientHandler(client)).start();
					}catch(IOException iex){
						if(!isClosed){
							System.out.println("Cant listen for clients");
							System.exit(-1);
						}
					}
				}
			}
		}).start();
		return true;
	}
	
	public void stop(){
		try {
			isClosed = true;
			socket.close();
		} catch (IOException e) {
			isClosed = false;
			System.out.println("Failed to stop server!");
		}
	}
	
	private class ClientHandler implements Runnable{
		private Socket client;
		private ObjectOutputStream oos;
		private ObjectInputStream ois;
		private boolean connected;
		
		public ClientHandler(Socket client){
			this.client = client;
			try {
				ois = new ObjectInputStream(client.getInputStream());
			} catch(IOException iex) {
				System.out.println("Failed to get client input stream.");
			}
			try {
				oos = new ObjectOutputStream(client.getOutputStream());
			} catch(IOException iex) {
				System.out.println("Failed to get client output stream.");
			}
			connected = true;
		}
		
		/**
		 * Read object send by client.
		 * @return The object read.
		 */
		public Object readObject() {
			try {
				return ois.readObject();
			} catch(Exception ex) {
				close();
			}
			return null;
		}
		
		/**
		 * Write an object to the client.
		 * @param obj The object to write.
		 */
		public void writeObject(Object obj){
			try {
				this.oos.writeObject(obj);
				this.oos.flush();
			} catch (Exception ex){
				close();
			}
		}
		
		/**
		 * Close the connection to the client.
		 */
		private void close(){
			connected = false;
			try {
				this.client.close();
			} catch (Exception e) {}
		}
		
		@Override
		public void run() {
			if(ois == null || oos == null) return;
			
			String sharedKey = "";
			int nonce = 0;
			
			while(client != null && connected) {
				Object readObj = readObject();
				if(readObj != null) {
					
					if(readObj instanceof NSObj){
						NSObj ns = (NSObj) readObj;
						
						if(ns.type == NSObj.Type.HELLO){
							byte[] read = new byte[]{};
							try {
								read = Decrypt.DecryptString(ns.msg, key);
							} catch (Exception e) {
								e.printStackTrace();
								System.out.println("Program crash!");
								System.exit(-1);
							}
							byte[][] splits = S.splitBytes(read, NSObj.getSplitter(ns.type));
							
							sharedKey = new String(splits[0]);
							System.out.println("Obtained shared key from " + new String(splits[1]) + ": " + sharedKey);
							
							System.out.println("Must verify receiver, sending check.");
							NSObj nsRes = new NSObj();
							nsRes.type = NSObj.Type.REPLY;
							nonce = (int) System.currentTimeMillis() % Integer.MAX_VALUE;
							try {
								nsRes.msg = Encrypt.EncryptString(("" + nonce).getBytes(), sharedKey);
							} catch (Exception e) {
								e.printStackTrace();
								System.out.println("Program crash!");
								System.exit(-1);
							}
							
							writeObject(nsRes);
						}else if(ns.type == NSObj.Type.VERIFY){
							byte[] read = new byte[]{};
							try {
								read = Decrypt.DecryptString(ns.msg, sharedKey);
							} catch (Exception e) {
								e.printStackTrace();
								System.out.println("Program crash!");
								System.exit(-1);
							}
							
							int replyNonce = Integer.valueOf(new String(read));
							
							if(replyNonce == nonce - 1){
								System.out.println("Verification successfully done!");
								NSObj nsRes = new NSObj();
								nsRes.type = NSObj.Type.COMMUNICATION;
								try {
									nsRes.msg = Encrypt.EncryptString("Hello my dear friend!", sharedKey);
								} catch (Exception e) {
									e.printStackTrace();
									System.out.println("Program crash!");
									System.exit(-1);
								}
								
								writeObject(nsRes);
							}else{
								System.out.println("INTRUDER ALERT! NOT SENDING REPLY!");
							}
						}
					}
				}
			}
			System.out.println("Client disconnected!");
		}
	}
	
	public static void main(String[] args) {
		int port = 34443;
		B b = new B(port);
		
		b.start();
	}
}
